/*
 Copyright (C) 2010 Kristian Duske

 This file is part of TrenchBroom.

 TrenchBroom is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 TrenchBroom is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with TrenchBroom. If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

#include "Color.h"
#include "render/GLVertexType.h"
#include "render/IndexRangeRenderer.h"
#include "render/Renderable.h"

#include "vm/bbox.h"

#include <map>
#include <vector>

namespace tb::render
{
class ActiveShader;
template <typename VertexSpec>
class IndexRangeMapBuilder;
class IndexRangeRenderer;

enum class PrimitiveRendererOcclusionPolicy
{
  Hide,
  Show,
  Transparent
};

enum class PrimitiveRendererCullingPolicy
{
  CullBackfaces,
  ShowBackfaces
};

class PrimitiveRenderer : public DirectRenderable
{
public:
private:
  using Vertex = GLVertexTypes::P3::Vertex;

  struct LineRenderAttributes
  {
    Color m_color;
    float m_lineWidth;
    PrimitiveRendererOcclusionPolicy m_occlusionPolicy;

    std::partial_ordering operator<=>(const LineRenderAttributes& other) const;
    bool operator==(const LineRenderAttributes& other) const;

    void render(IndexRangeRenderer& renderer, ActiveShader& shader, float dpiScale) const;
  };

  using LineMeshMap = std::map<LineRenderAttributes, IndexRangeMapBuilder<Vertex::Type>>;
  LineMeshMap m_lineMeshes;

  using LineMeshRendererMap = std::map<LineRenderAttributes, IndexRangeRenderer>;
  LineMeshRendererMap m_lineMeshRenderers;

  class TriangleRenderAttributes
  {
  private:
    Color m_color;
    PrimitiveRendererOcclusionPolicy m_occlusionPolicy;
    PrimitiveRendererCullingPolicy m_cullingPolicy;

  public:
    TriangleRenderAttributes(
      Color color,
      PrimitiveRendererOcclusionPolicy occlusionPolicy,
      PrimitiveRendererCullingPolicy cullingPolicy);

    std::partial_ordering operator<=>(const TriangleRenderAttributes& other) const;
    bool operator==(const TriangleRenderAttributes& other) const;

    void render(IndexRangeRenderer& renderer, ActiveShader& shader) const;
  };

  using TriangleMeshMap =
    std::map<TriangleRenderAttributes, IndexRangeMapBuilder<Vertex::Type>>;
  TriangleMeshMap m_triangleMeshes;

  using TriangleMeshRendererMap = std::map<TriangleRenderAttributes, IndexRangeRenderer>;
  TriangleMeshRendererMap m_triangleMeshRenderers;

public:
  void renderLine(
    const Color& color,
    float lineWidth,
    PrimitiveRendererOcclusionPolicy occlusionPolicy,
    const vm::vec3f& start,
    const vm::vec3f& end);
  void renderLines(
    const Color& color,
    float lineWidth,
    PrimitiveRendererOcclusionPolicy occlusionPolicy,
    const std::vector<vm::vec3f>& positions);
  void renderLineStrip(
    const Color& color,
    float lineWidth,
    PrimitiveRendererOcclusionPolicy occlusionPolicy,
    const std::vector<vm::vec3f>& positions);

  void renderCoordinateSystemXY(
    const Color& x,
    const Color& y,
    float lineWidth,
    PrimitiveRendererOcclusionPolicy occlusionPolicy,
    const vm::bbox3f& bounds);
  void renderCoordinateSystemXZ(
    const Color& x,
    const Color& z,
    float lineWidth,
    PrimitiveRendererOcclusionPolicy occlusionPolicy,
    const vm::bbox3f& bounds);
  void renderCoordinateSystemYZ(
    const Color& y,
    const Color& z,
    float lineWidth,
    PrimitiveRendererOcclusionPolicy occlusionPolicy,
    const vm::bbox3f& bounds);
  void renderCoordinateSystem3D(
    const Color& x,
    const Color& y,
    const Color& z,
    float lineWidth,
    PrimitiveRendererOcclusionPolicy occlusionPolicy,
    const vm::bbox3f& bounds);

  void renderPolygon(
    const Color& color,
    float lineWidth,
    PrimitiveRendererOcclusionPolicy occlusionPolicy,
    const std::vector<vm::vec3f>& positions);
  void renderFilledPolygon(
    const Color& color,
    PrimitiveRendererOcclusionPolicy occlusionPolicy,
    PrimitiveRendererCullingPolicy cullingPolicy,
    const std::vector<vm::vec3f>& positions);

  void renderCylinder(
    const Color& color,
    float radius,
    size_t segments,
    PrimitiveRendererOcclusionPolicy occlusionPolicy,
    PrimitiveRendererCullingPolicy cullingPolicy,
    const vm::vec3f& start,
    const vm::vec3f& end);

private:
  void doPrepareVertices(VboManager& vboManager) override;
  void prepareLines(VboManager& vboManager);
  void prepareTriangles(VboManager& vboManager);

  void doRender(RenderContext& renderContext) override;
  void renderLines(RenderContext& renderContext);
  void renderTriangles(RenderContext& renderContext);
};
} // namespace tb::render
